/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 *
 * The Initial Developer of the Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 * Portions created by Mark A. Kobold are Copyright (C) 2000-2007. All Rights Reserved.
 *
 * Contributor(s): 
 *  Mark A. Kobold [mkobold <at> isqlviewer <dot> com].
 *  
 * If you didn't download this code from the following link, you should check
 * if you aren't using an obsolete version: http://www.isqlviewer.com
 */
package org.isqlviewer.xml;

import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.sql.Timestamp;
import java.util.Date;
import java.util.Enumeration;

import org.isqlviewer.BindVariable;
import org.isqlviewer.bookmarks.Bookmark;
import org.isqlviewer.bookmarks.BookmarkFolder;
import org.isqlviewer.util.BasicUtilities;
import org.isqlviewer.util.IsqlToolkit;
import org.isqlviewer.util.StringUtilities;
import org.xml.sax.Attributes;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

/**
 * XML Handler for parsing iSQL-Viewer bookmarks via SAX.
 * <p>
 * This class is primarily retained for compatability purposes within iSQL-Viewer such that bookmarks from previous
 * versions can be retrieved and impoted into newer versions of iSQL-Viewer that do not store bookmarks in an XML
 * format.
 * 
 * @author Mark A. Kobold &lt;mkobold at isqlviewer dot com&gt;
 * @version 1.0
 */
public class BookmarkDigester extends DefaultHandler {

    private static final String DTD_DECLARATION = "<!DOCTYPE isql-bookmarks PUBLIC \"-//iSQL-Viewer.org.//DTD JDBC Bookmarks 2.1.8//EN\" \"http://isqlviewer.org/dtd/bookmarks.dtd\">";

    private static final String TAG_ROOT_BOOKMARK = "isql-bookmarks";
    private static final String TAG_BOOKMARK = "bookmark";
    private static final String TAG_PARAMETER = "parameter";
    private static final String TAG_FOLDER = "folder";
    private static final String ATTR_NAME = "name";

    private static String ATTR_BM_EPOCH = "created";
    private static String ATTR_BM_LAST = "last";
    private static String ATTR_BM_COUNT = "count";
    private static String ATTR_BM_EPOCH_OLD = "epoch";
    private static String ATTR_BM_LAST_OLD = "last_query";
    private static String ATTR_BM_COUNT_OLD = "use_count";

    private static String ATTR_PARAM_TYPE = "type";
    private static String ATTR_PARAM_VALUE = "value";
    private static String ATTR_PARAM_OPTIONS = "options";
    private static String ATTR_PARAM_INDEX = "index";

    private StringBuffer cdataBuffer = new StringBuffer("");
    private BookmarkFolder rootFolder = null;
    private Bookmark currentBookmark = null;
    private BookmarkFolder currentFolder = null;

    public static BookmarkFolder parseBookmarks(InputSource source) throws Exception {

        return parseBookmarks(source, IsqlToolkit.getSharedEntityResolver());
    }

    public static BookmarkFolder parseBookmarks(InputSource source, EntityResolver resolver) throws Exception {

        XMLReader xmlreader = BasicUtilities.createXMLReader(false, true);
        BookmarkFolder root = BookmarkFolder.createRootFolder();
        BookmarkDigester digester = new BookmarkDigester(root);
        if (resolver != null) {
            xmlreader.setEntityResolver(resolver);
        }
        xmlreader.setContentHandler(digester);
        xmlreader.setErrorHandler(digester);
        xmlreader.parse(source);
        return root;
    }

    public static void writeBookmarkFolder(OutputStream out, BookmarkFolder bookmarkFolder) {

        writeBookmarkFolder(out, bookmarkFolder, Charset.forName("UTF-8"));
    }

    public static void writeBookmarkFolder(OutputStream out, BookmarkFolder bookmarkFolder, Charset cs) {

        OutputStreamWriter osw = new OutputStreamWriter(out, cs);
        PrintWriter pw = new PrintWriter(osw, false);
        pw.print("<?xml version=\"1.0\" encoding=\"");
        pw.print(cs.name());
        pw.println("\" ?>");
        try {
            pw.println(DTD_DECLARATION);
            pw.print('<');
            pw.print(TAG_ROOT_BOOKMARK);
            pw.println('>');
            printBookmarkFolder(pw, bookmarkFolder, 1);
            pw.print("</");
            pw.print(TAG_ROOT_BOOKMARK);
            pw.println('>');
        } finally {
            pw.flush();
            pw.close();
            pw = null;
            osw = null;
        }
    }

    private BookmarkDigester(BookmarkFolder rootFolder) {

        this.rootFolder = rootFolder;
    }

    @Override
    public void startDocument() throws SAXException {

        currentFolder = rootFolder;
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {

        cdataBuffer.append(new String(ch, start, length).trim());
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {

        String tag = qName.toLowerCase().trim();
        if (TAG_FOLDER.equals(tag)) {
            BookmarkFolder childFolder = currentFolder.addChildFolder(attributes.getValue(ATTR_NAME));
            currentFolder = childFolder;
        } else if (TAG_BOOKMARK.equals(tag)) {
            currentBookmark = new Bookmark();
            currentBookmark.setName(attributes.getValue(ATTR_NAME));
            int idx = attributes.getIndex(ATTR_BM_EPOCH_OLD);
            if (idx >= 0) {
                long timestamp = Long.parseLong(attributes.getValue(idx));
                currentBookmark.setCreationTime(new Date(timestamp));
            } else {
                long timestamp = Long.parseLong(attributes.getValue(attributes.getIndex(ATTR_BM_EPOCH)));
                currentBookmark.setCreationTime(new Date(timestamp));
            }

            idx = attributes.getIndex(ATTR_BM_LAST_OLD);
            if (idx >= 0) {
                long timestamp = Long.parseLong(attributes.getValue(idx));
                currentBookmark.setLastAccess(new Date(timestamp));
            } else {
                long timestamp = Long.parseLong(attributes.getValue(attributes.getIndex(ATTR_BM_LAST)));
                currentBookmark.setLastAccess(new Timestamp(timestamp));
            }
            idx = attributes.getIndex(ATTR_BM_COUNT_OLD);
            if (idx >= 0) {
                currentBookmark.setUseCount(Long.parseLong(attributes.getValue(idx)));
            } else {
                int useCount = Integer.parseInt(attributes.getValue(attributes.getIndex(ATTR_BM_COUNT)));
                currentBookmark.setUseCount(useCount);
            }
            currentFolder.addBookmark(currentBookmark);
        } else if (TAG_PARAMETER.equals(tag)) {
            BindVariable bpd = new BindVariable();
            bpd.setFormatOptions(attributes.getValue(ATTR_PARAM_VALUE));
            bpd.setUserData(attributes.getValue(ATTR_PARAM_VALUE));
            bpd.setType(Integer.parseInt(attributes.getValue(ATTR_PARAM_TYPE)));
            currentBookmark.add(bpd);
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) {

        String tag = qName.toLowerCase().trim();
        if (TAG_FOLDER.equals(tag)) {
            if (currentFolder != rootFolder) {
                currentFolder = currentFolder.getParentFolder();
            }
        } else if (TAG_BOOKMARK.equals(tag)) {
            String sql = StringUtilities.decodeBase64(cdataBuffer.toString().trim());
            currentBookmark.setCommandText(sql);
        }
        cdataBuffer.setLength(0);
    }

    private static void createTabdepth(PrintWriter out, int size) {

        for (int i = 0; i < size; i++) {
            out.print('\t');
        }
    }

    private static void printBookmarkFolder(PrintWriter out, BookmarkFolder folder, int depth) {

        Enumeration childElements = folder.childElements();
        boolean isRoot = folder.getParentFolder() == null;
        if (!isRoot) {
            createTabdepth(out, depth - 1);
            out.print('<');
            out.print(TAG_FOLDER);
            out.print(' ');
            out.print(ATTR_NAME);
            out.print("=\"");
            out.print(StringUtilities.encodeXMLEntities(folder.getName()));
            out.println("\">");
        }
        while (childElements.hasMoreElements()) {
            Object child = childElements.nextElement();
            if (child instanceof BookmarkFolder) {
                printBookmarkFolder(out, (BookmarkFolder) child, depth + 1);
            } else if (child instanceof Bookmark) {
                printBookmark(out, (Bookmark) child, depth);
            }
        }
        if (!isRoot) {
            createTabdepth(out, depth - 1);
            out.print("</");
            out.print(TAG_FOLDER);
            out.println('>');
        }
    }

    private static void printBookmark(PrintWriter out, Bookmark bookmark, int depth) {

        createTabdepth(out, depth);
        out.print('<');
        out.print(TAG_BOOKMARK);
        out.print(' ');
        out.print(ATTR_NAME);
        out.print("=\"");
        out.print(StringUtilities.encodeXMLEntities(bookmark.getName()));
        out.print("\" ");
        out.print(ATTR_BM_COUNT);
        out.print("=\"");
        out.print(Long.toString(bookmark.getUseCount()));
        out.print("\" ");
        out.print(ATTR_BM_LAST);
        out.print("=\"");
        Date timestamp = null;
        timestamp = bookmark.getLastAccess();
        out.print(Long.toString(timestamp == null ? 0 : timestamp.getTime()));
        out.print("\" ");
        out.print(ATTR_BM_EPOCH);
        out.print("=\"");
        timestamp = bookmark.getCreationTime();
        out.print(Long.toString(timestamp == null ? 0 : timestamp.getTime()));
        out.println("\">");
        if (bookmark.containsParameters()) {
            Enumeration<BindVariable> elements = bookmark.variables();
            while (elements.hasMoreElements()) {
                BindVariable next = elements.nextElement();
                createTabdepth(out, depth + 1);
                out.print(TAG_PARAMETER);
                out.print(" ");
                out.print(ATTR_PARAM_INDEX);
                out.print("=\"");
                out.print(Integer.toString(next.getType()));
                out.print("\" ");
                out.print(ATTR_PARAM_TYPE);
                out.print("=\"");
                out.print(Integer.toString(next.getType()));
                out.print("\" ");
                out.print(ATTR_PARAM_VALUE);
                out.print("=\"");
                out.print(StringUtilities.encodeXMLEntities(next.getUserData()));
                out.print("\" ");
                out.print(ATTR_PARAM_OPTIONS);
                out.print("=\"");
                out.print(StringUtilities.encodeXMLEntities(next.getFormatOptions()));
                out.print("\"/>");
            }
        }

        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw, true);
        createTabdepth(pw, depth);
        pw.flush();
        sw.append(' ');
        createTabdepth(out, depth);
        out.print(' ');

        String encoded = StringUtilities.encodeBase64(bookmark.getCommandText());
        out.println(StringUtilities.formatBreak(80, encoded, sw.toString(), false));
        createTabdepth(out, depth);
        out.print("</");
        out.print(TAG_BOOKMARK);
        out.println('>');
    }
}
